/**
*Vue.js v2.0.1 
*study 2016.10.13
*klren
*/
//js封装
(function(global,factory){
	typeof export === 'object' && typeof module !== 'undefined' ? module.exports = factory();
	typeof define === 'function' && define.amd ? define(factory) :
	(global.Vue = factory());
}(this,(function(){ 'use strict';//使用严格模式


/**
*将一个值转变成字符串
*JSON.stringify()可以将任意的javascript值序列化成JSON字符串
*JSON.stringify(要序列化的值，数组，空格数)
*/
function _toString(val){
	return val == null ? '':typeof val === 'object' ? JSON.stringify(val,null,2) : String(val)
}

/**
*将一个输入值转换成一个数字
*如果转换失败，则返回原始的字符串
*/
function toNumber(val){
	var n = parseFloat(val,10)
	return (n || n === 0) ? n:val
}

/**
*创建一个map ，如果有一个键则返回一个函数来核对，是否在map里
*toLowerCase转换成小写
*/
function makeMap(str,expectsLowerCase){
	var map = Object.create(null)
	var list = str.split(',')
	for (var i = 0; i < list.length; i++) {
		map[list[i]] = true
	}
	return expectsLowerCase ? 
		function(val){return map[val.toLowerCase()];}
		:function(val){return map[val];}
}

/**
*检测是否一个标签是一个内置的标签
*/
var isBuiltInTag = makeMap('slot,component',true)

/**
*从数组中移除一个元素
*indexOf()返回某个指定字符串在字符串中首次出现的位置
*indexOf(需要检索的字符串值)
*splice()从数组中添加/删除项目，然后返回被删除的项目
*splice(规定添加/删除的位置，要删除项目的数量，可选-添加新项目)
*/
function remove$1 (arr,item){
	if (arr.length) {
		var index = arr.indexOf(item)
		if (index > -1) {
			return arr.splice(index,1)
		}
	}
}

/**
*检查对象是否有属性
*hasOwnProperty()指示一个对象自身是否具有指定名称的属性,
*object.hasOwnProperty(propertyName)
*有则返回true，否则返回false
*/
var hasOwnProperty = Object.prototype.hasOwnProperty
function hasOwn(obj,key){
	return hasOwnProperty.call(obj,key)
}

/**
*检查数值是否是原生的
*/
function isPrimitive(value){
	return typeof value === 'string' || typeof value === 'number'
}

/**
*创建一个纯函数的缓存版本
*/
function cached(fn){
	var cache = Object.create(null)
	return function cachedFn(str){
		var hit = cache[str]
		return hit || (cache[str] = fn(str))
	}
}

/**
*将连字符分隔的字符串转换成驼峰拼写法的字符串
*replace用于在字符串中用一些字符替换另一些字符，
*或者替换一个与正则表达式匹配的子串 
*replace(规定子字符串或要替换的模式的RegExp对象，一个字符串值)
*/
var camelizeRE = /-(\w)/g
var camelize = cached(function(str){
	return str.replace(camelizeRE,function(_,c){ return c ? c.toUpperCase() : ''; })
})

/**
*把一个字符串定位首部
*charAt()返回指定位置的字符
*slice(开始，选-结束)从已有的数组中返回选定的元素
*/
var capitalize = cached(function(str){
	return str.charAt(0).toUpperCase() + str.slice(1)
})

/**
*将骆驼拼写法字符串变成连字符分割的字符串
*/
var hyphenateRE = /([^-])([A-Z])/g
var hyphenate = cached(function(str){
	return str
		.replace(hyphenateRE,'$1-$2')
		.replace(hyphenateRE,'$1-$2')
		.toLowerCase()
})

/**
*简单的绑定，比原生的快
*call(当前，另)调用一个对象的一个方法，以另一个对象替换当前对象
*apply(当前，另) 应用某一个对象的一个方法，用另一个对象替换当前对象
*/
function bind$1(fn,ctx){
	function boundFn(a){
		var l = arguments.length
		return l ? l>1 ? fn.apply(ctx,arguments) : fn.call(ctx,a) :fn.call(ctx)
	}
	//记录 fn 的长度
	boundFn._length = fn.length
	return boundFn
}

/**
 * 将一个类数组替换成一个真实的数组
 */
function toArray(list,start){
	start = start || 0
	var i = list.length - start 
	var ret = new Array(i)
	while(i--){
		ret[i] = list[i+start]
	}
	return ret
}

/**
 * 将属性混合到目标的对象
 */
function extend(to,_from){
	for(var key in _from){
		to[key] = _from[key]
	}
	return to
}

/**
 * 快速检查对象-当我们知道这个数据是一个json类型，将会把原始数值对象返回给我们
 */
function isObject(obj){
	return obj !== nul && typeof obj === 'object'
}

/**
 * 严格的对象类型检查。只返回正确的无格式的js对象
 */
var toString = Object.prototype.toString
var OBJECT_STRING = '[object Object]'
function isPlainObject (obj){
	return toString.call(obj) === OBJECT_STRING
}

/**
 * 合并一个数组对象到一个单独对象
 */
function toObject(arr){
	var res = {}
	for(var i=0;i<arr.length;i++){
		if (arr[i]) {
			extend(res,arr[i])
		}
	}
	return res
}

/**
 * 不执行操作
 */
function noop(){}

/**
 * 经常返回false
 */
var no = function(){
	return false;
}

/**
 * 从编辑器生成成一个静态键字符串
 * 1、array.reduce(callbackfn[,initialValue]);对数组中所有元素调用指定的回调函数。
 * callback 一个接受最多四个参数的函数，对于数组中的每个元素，reduce都会调用此函数一次，
 * initialValue 作为初始值来累积。
 * 返回值为累计结果，在下一次调用时，返回值作为参数提供
 * 2、String1.concat([string2[string3..[stringN]]])返回两个或以上的字符串串联而成的字符串
 * String1+String2+....
 */
function genStaticKeys(modules){
	return modules.reduce(function (keys,m){
		return key.concat(m.staticKeys || [])
	},[]).join(',')
}

/**
 *检查是否两个值是简单相等-也就是说
 *如果他们是普通的对象，他们是否有相同的类型？
 *eslint静态代码检查
 */
function looseEqual(a,b){
	  /* eslint-disable eqeqeq */
	return a == b ||(
		isObject(a) && isObject(b)
			? JSON.stringify(a) === JSON.stringify(b)
			: false
	) 
	  /* eslint-enable eqeqeq */
}

/*找到数组中与val相等的对象的位置*/
function looseIndexOf(arr,val){
	for (var i = 0; i < arr.length; i++) {
		if (looseEqual(arr[i],val)) {
			return i
		}
	}
}

/*  */
var config = {
	/**
	 * 配置合并策略（在core/util/options中使用）
	 * Object.create(proto,[propertiesObject])创建一个拥有指定原型和若干个指定属性的对象
	 * proto 一个对象，作为新创建对象的原型
	 * propertiesObject 可选，一组属性与值 
	 */
	optionsMergeStrategies:Object.create(null),

	/**
	 * 是否关闭警告
	 */
	silent:false,

	/**
	 * 是否使用开发者工具
	 */
	devtools:"devlopment" !== 'production',

	/**
	 * 错误处理程序中的错误
	 */
	errorHandler:null,

	/**
	 * 忽略某些自定义元素
	 */
	ignoreElements:null,

	/**
	 * 自定义v-on的用户键别名
	 */
	keyCodes:Object.create(null),

	/**
	 * 检查一个标签是否被保留，来判断它能不能被注册成组件
	 * 这与平台相关，可能被覆盖。
	 */
	isReservedTag: no,

	/**
	 * 检查一个标签是否是一个未知元素
	 * 平台依赖
	 */
	isUnknownElement:no,

	/**
	 * 元素的命名空间
	 */
	getTagNamespace: noop,

	/**
	 * 检查一个属性是否必须被绑定一个使用属性，例如 value
	 * 平台依赖
	 */
	mustUseProp: no,

	/**
	 * 一个组件能拥有的资源类型列表
	 */
	_assetTypes:[
		'component',
		'directive',
		'filter'
	],

	/**
	 * 生命周期钩子列表
	 */
	 _lifecycleHooks:[
	 	'beforeCreate',
	 	'created',
	 	'beforeMount',
	 	'mounted',
	 	'beforeUpdate',
	 	'updated',
	 	'beforeDestroy',
	 	'destroyed',
	 	'activated',
	 	'deactivated'
	 ],

	 /**
	  * 最大循环更新允许一个调度程序刷新周期
	  */
	 _maxUpdateCount:100,

	 /**
	  * 服务器呈现
	  */
	 _isServer:"client" === 'server'
}
/* */

/**
 * 检查一个字符串是否以$或者_开始
 */
function isReserved(str){
	var c = (str + '').charCodeAt(0)
	return c === 0x24 || c === 0x5F
}

/**
 *定义一个属性 
 */
function def(obj,key,val,enumerable){
	Object.defineProperty(obj,key,{
		value:val,
		enumerable:!!enumerable,
		writable:true,
		configurable:true
	})
}

/**
 * 解析简单路径
 */
var bailRE = /[^\w\.\$]/
function parsePath(path){
	if (bailRE.test(path)) {
		return
	}else{
		var segments = path.split('.')
		return function (obj){
			for (var i = 0; i <segments.length; i++) {
				if (!obj) {return}
				obj = obj[segments[i]]
			}
			return obj
		}
	}
}

/**
 * 全局Mutation Observer（变动观察器）
 * 是监视DOM变动的接口。
 * 当DOM对象树发生任何变动时，Mutation Observer会得到通知。
 */

//我们能用 __proto__(原型)?
var hasProto = '__proto__' in {}

//浏览器环境嗅探
var inBrowser = 
	typeof window !== 'undefined' &&
	Object.prototype.toString.call(window) !== '[object Objcet]'

/*获取useragent信息，转换成小写*/
var UA = inBrowser && window.navigator.userAgent.toLowerCase()

/*判断浏览器*/
var isIE = UA && /msie|trident/.test(UA)
var isIE9 = UA && UA.indexOf('msie 9.0')>0
var isEdge =UA && UA.indexOf('edge/')>0
/*判断手机系统*/
var isAndroid - UA && UA.indexOf('android')>0
var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)

//检测开发者工具（devtools）
var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__

//istanbul(代码覆盖率工具)
/*istanbul ignore next*/
function isNative(Ctor){
	return /native code/.test(Ctor.toString())
}

/**
 * 延迟任务异步执行
 * arrayObject.slice(start,end)从已有数组中返回选定元素
 */
var nextTick = (function(){
	var callbacks = []
	var pending = false
	var timerFunc

	function nextTickHandler(){
		pending = false
		var copies = callbacks.slice(0)
		callbacks.length = 0
		for (var i = 0; i < copies.length; i++) {
			copies[i]()
		}
	}
	/**
	 * 所谓 Promise，
	 * 就是一个对象，用来传递异步操作的消息。
	 * 它代表了某个未来才会知道结果的事件（通常是一个异步操作），
	 * 并且这个事件提供统一的 API，可供进一步处理。
	 */
	
	//
	//  nextTick 行为 
	//  利用了可以访问通过原生Promise，
	//  或者变动观察器的微任务队列。
	//  变动观察器有更广泛的支持，
	//  然而当出发触摸事件的时候，
	//  在ios>=9.3.3的UIWebView中有非常严重的bug
	//  它会完全停止工作在触发很少次后。。。
	//  所以，如果本地Promise可用，我们将会使用它：
	/* istanbul ignore if*/
	if (typeof Promise !== 'undefined' && isNative(Promise)) {
		var p = Promise.resolve()
		timerFunc = function(){
			p.then(nextTickHandler)
			// 在有问题的UIWebView中，Promise。不能完全的打破，
			// 但是它能陷入一个奇怪的状态 回调被推送到微任务队列，
			// 但是队列没有被刷新，直到浏览器需要去做一些其他的工作的时候
			// 例如，处理一个定时器。
			// 因此我们可以“强制”刷新微任务队列通过增加一个空的定时器
			if(isIOS){
				setTimeout(noop)
			}
		}
		}else if(typeof MutationObserver !== 'undefined' &&(
			isNative(MutationObserver) ||
			// PhantomJS and IOS7.x
			MutationObserver.toString() === '[object MutationObserverConstructor]'
		)){
			//使用变动观察器，在原生Promise不能使用的地方
			//例如 PhantomJS，IE11,Ios7,Android4.4
			var counter = 1
			var observer = new MutationObserver(nextTickHandler)
			var textNode = document.createTextNode(String(counter))
			observer.observe(textNode,{
				characterData:true
			})
			timerFunc = function(){
				counter = (counter + 1 ) % 2
				textNode.data = String(counter)
			}
		}else{
			//回退到setTimeout
			/*istanbul ignore next*/
			timerFunc = setTimeout
		}

		return function queueNextTick(cb,ctx){
			var func = ctx
				?function(){
					cb.call(ctx)
				}
				:cb
			callbacks.push(func)
			if (!pending) {
				pending = true
				timerFunc(nextTickHandler,0)
			}
		}
})()

var _Set
/*istanbul ignore if*/
if (typeof Set !== 'undefined' && isNative(Set)) {
	//使用原生Set当可用的时候
	_Set = Set
}else{
	//一个不标准的只能工作于原始键的Set polyfill 
	/**
	 * 一个shim是一个库,
	 * 它将一个新的API引入到一个旧的环境中,
	 * 而且仅靠旧环境中已有的手段实现一个polyfill
	 * 就是一个用在浏览器API上的shim.
	 * 我们通常的做法是先检查当前浏览器是否支持某个API,
	 * 如果不支持的话就加载对应的polyfill.
	 * 然后新旧浏览器就都可以使用这个API了.
	 */
	_Set = (function(){
		function Set(){
			this.set = Object.create(null)
		}
		Set.prototype.has = function has (key){
			return this.set[key]!==undefined
		};
		Set.prototype.add = function add(key){
			this.set[key] = 1
		};
		Set.prototype.clear = function clear(){
			this.set = Object.create(null)
		};

		return Set;
	}())
}

/*没有类型检查这个文件，因为流通过代理不能正常运行*/
var hasProxy;
var proxyHandlers;
var initProxy{
	var allowedGlobals = makeMap(
		'Infinity,undefined,NaN,isFinite,isNaN,'+
		'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,'+
		'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,'+
		'require'//for Webpack/Browserify
	)

	hasProxy =
		typeof Proxy !== 'undefined' &&
		Proxy.toString().match(/native code/)

	proxyHandlers = {
		has: function has (target,key){
			var has = key in target
			var isAllowed = allowedGlobals(key) || key.charAt(0) === '_'
			if(!has && !isAllowed){
				warn(
					"Property or method \""+key + "\"is not defined on the instance but" +
					"reference during render.Make sure to declare reactive data"+
					"properties in the data option.",
					target
				)
			}
			return has || !isAllowed
		}
	}

	initProxy = function initProxy(vm){
		if(hasProxy){
			vm._renderProxy = new Proxy(vm,proxyHandlers)
		}else{
			vm._renderProxy = vm
		}
	}
}

/*  */

var uid$2 =0

/**
 * dep 是一个可观察的，可以有多个指令订阅它
 */
var Dep = function Dep(){
	this.id = uid$2++
	this.subs = []
};

Dep.prototype.addSub = function addSub(sub){
	this.subs.push(sub)
};

Dep.prototype.removeSub = function removeSub (sub) {
  remove$1(this.subs, sub)
};

Dep.prototype.depend = function depend(){
	if (Dep.target) {
		Dep.target.addDep(this)
	}
};

Dep.prototype.notify = function notify(){
	//首先稳定订阅者列表
	var subs = this.subs.slice()
	for (var i = 0,l=subs.length; i <l; i++) {
		subs[i].update()
	}
	// for(var i=0;i<subs.length;i++){
	// 	subs[i].update()
	// }
	// 使用第二个如果subs.length过大会导致内存消耗大，如果
	// 使用第一个只用计算一次
};

// 当前的target watcher 被评估
// 这是一个全局的队列因为有可能只有一个
// watcher会在任何时候被评估
Dep.target = null
var targetStack = []

function pushTarget(_target){
	if (Dep.target) {
		targetStack.push(Dep.target)
	}
	Dep.target = _target
}

function popTarget(){
	Dep.target = targetStack.pop()
}

/*  */

var queue = []
var has$1 = {}
var circular = {}
var waiting = false
var flushing = false
var index = 0

/**
 * 重置调度程序状态
 */
function resetSchedulerState(){
	queue.length = 0
	has$1 = {}
	{
		circular = {}
	}
	waiting = flushing = false
}

/**
 * 把队列和运行的watchers一起刷新
 */
function flushSchedulerQueue(){
	flushing = true
	// 在刷新前对队列进行排序
	// 这将确保：
	// 1.组件从父组件到子组件进行更新（因为父组件经常在子组件前创建）
	// 2.一个组件的用户watchers 之前呈现watchers(因为用户watchers被创建在watcher呈现前)
	// 3.如果在一个父组件的watcher呈现中，一个组件被摧毁，它的watchers可以跳过。
	queue.sort(function(a,b){
		return a.id - b.id;
	})
	
	// 不缓存长度，因为更多watchers可能被推送
	// 当我们运行现有的watchers
	for(index = 0;index <queue.length;index++){
		var watcher = queue[index]
		var id = watcher.id
		has$1[id] = null
		watcher.run()
		//在开发构建里，检查和停止计算更新
		if ("development" !== 'production' && has$1[id] != null) {
			circular[id] = (circular[id] || 0)+1
			if (circular[id] > config._maxUpdateCount) {
				warn(
				    'You may have an infinite update loop ' + (
				      watcher.user
				       ? ("in watcher with expression \"" + (watcher.expression) + "\"")
				       : "in a component render function."
				      ),
				      watcher.vm
				)
				break
			}
		}
	}

	//devtool hook
	/*istanbul ignore if*/
	if (devtools && config.devtools) {
		devtools.emit('flush')
	}
	resetSchedulerState()
}

/**
 * 推送一个watcher到watcher队列
 * 工作副本ID将会被跳过，除非在队列正在被刷新的时候它在推送
 */
function queueWatcher(watcher){
	var id = watcher.id
	if (has$1[id] = null) {
		has$1[id] = true
		if (!flushing) {
			queue.push(watcher)
		}else{
			//如果已经刷新，根据watcher的id进行拼接
			//如果已经过去的id，它将立即运行下一个
			var i = queue.length -1
			while(i>= 0 && queue[i].id>watcher.id){
				i--
			}
			queue.splice(Math.max(i,index)+1,0,watcher)
		}
		//刷新队列
		if (!waiting) {
			waiting = true
			nextTick(flushSchedulerQueue)
		}
	}
}

/*   */

var uid$1 = 0

/**
 * watcher解析一个表达式， 收集依赖关系，
 * 以及当表达式的值改变的时候触发回调。
 * 这个被用来给$watch() api 和指令使用
 */
var Watcher = function Watcher(
	vm,
	expOrFn,
	cb,
	options
){
	if (options === void 0) options = {};
	this.vm = vm
	vm._watchers.push(this)
	//options选项
	this.deep=!!options.deep
	this.user=!!options.user
	this.lazy=!!option.lazy
	this.sync=!!option.sync
	this.expression = expOrFn.toString()
	this.cb=cb
	this.id=++uid$1 //uid 批处理
	this.active=true
	this.dirty=this.lazy //for lazy watchers
	this.deps = []
	this.newDeps = []
	this.depIds = new _Set()
	this.newDepIds = new _Set()
	//getter的解析表达式
	if (typeof expOrFn === 'function') {
		this.getter = expOrFn
	}else{
		this.getter = parsePath(expOrFn)
		if (!this.getter) {
			this.getter = function(){}
			"development" !== 'production' && warn(
			  "Failed watching path: \"" + expOrFn + "\" " +
			  'Watcher only accepts simple dot-delimited paths. ' +
			  'For full control, use a function instead.',
			  vm
			)
		}
	}
	this.value = this.lazy
	?undefined
	:this.get()
};

/**
 * 评估getter，重新收集依赖
 */
Watcher.prototype.get = function get(){
	pushTarget(this)
	var value = this.getter.call(this.vm,this.vm)
	//"触碰" 每个属性，所以他们所有都被很深的依赖追踪
	if (this.deep) {
		traverse(value)
	}
	popTarget()
	this.cleanupDeps()
	return value
}
})));